package com.softdev.system.generator.controller;

import com.softdev.system.generator.entity.ClassInfo;
import com.softdev.system.generator.entity.ReturnT;
import com.softdev.system.generator.util.CodeGeneratorTool;
import com.softdev.system.generator.util.FreemarkerTool;
import freemarker.template.TemplateException;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * spring boot code generator
 *
 * @author zhengk/moshow
 */
@Controller

public class IndexController {

    private final Logger log = LoggerFactory.getLogger(IndexController.class);

    @Autowired
    private FreemarkerTool freemarkerTool;

    @RequestMapping("/")
    public String index() {
        return "index";
    }

    @RequestMapping("/generateCode")
    @ResponseBody
    public ReturnT<Map<String, Map<String, String>>> generateCode(String tableSql,
                                                                  @RequestParam(required = false, defaultValue = "harry") String authorName,
                                                                  @RequestParam(required = false, defaultValue = "true") Boolean useLombok,
                                                                  @RequestParam(required = false, defaultValue = "false") Boolean useProtobuf,
                                                                  @RequestParam(required = false, defaultValue = "true") Boolean useMapstruct,
                                                                  @RequestParam(required = false, defaultValue = "true") Boolean longToString,
                                                                  @RequestParam(required = false, defaultValue = "com.dbapp.dsc") String packageName,
                                                                  @RequestParam(required = false, defaultValue = "") String moduleName
    ) {
        try {

            if (StringUtils.isBlank(tableSql)) {
                return new ReturnT<>(ReturnT.FAIL_CODE, "表结构信息不可为空");
            }
            String tablePrefix = "t_";

            Map<String, Object> params = generateParams(tableSql, authorName, packageName, moduleName,tablePrefix);

            params.put("useLombok",useLombok);
            params.put("useProtobuf",useProtobuf);
            params.put("useMapstruct",useMapstruct);
            params.put("longToString",longToString);
            params.put("tablePrefix",tablePrefix);
            Map<String, String> fileNameMap = new LinkedHashMap<>(16);
            // result
            Map result = getCodeResultMap(params, fileNameMap);


            String filePath = zipFiles(fileNameMap, params, result);

            result.put("filePath", URLEncoder.encode(filePath));


            return new ReturnT<>(result);
        } catch (IOException e) {
            log.error("表结构解析失败", e);
            return new ReturnT<>(ReturnT.FAIL_CODE, "表结构解析失败" + e.getMessage());
        } catch (TemplateException e) {
            log.error("模板解析失败", e);
            return new ReturnT<>(ReturnT.FAIL_CODE, "模板解析失败" + e.getMessage());
        } catch (IllegalArgumentException e) {
            log.error("模板解析失败", e);
            return new ReturnT<>(ReturnT.FAIL_CODE, "生产代码失败；" + e.getMessage());
        }

    }

    @RequestMapping("/download")
    public void test(@RequestParam String fileName,
                     HttpServletResponse response) throws IOException {
        String zipFileName = System.getProperty("java.io.tmpdir") + File.separator + fileName;
        File file = new File(zipFileName);

        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "attachment;filename=" + file.getName());

        ServletOutputStream outStream = response.getOutputStream();


        byte[] byteBuffer = new byte[4096];
        DataInputStream in = new DataInputStream(new FileInputStream(file));

        // reads the file's bytes and writes them to the response stream
        int length = 0;
        while ((in != null) && ((length = in.read(byteBuffer)) != -1)) {
            outStream.write(byteBuffer, 0, length);
        }

        in.close();
        outStream.close();
    }


    private Map<String, Map<String, String>> getCodeResultMap(Map<String, Object> params, Map<String, String> fileNameMap) throws IOException, TemplateException {
        // result
        LinkedHashMap<String, Map<String, String>> result = new LinkedHashMap<>(16);

        ClassInfo classInfo = (ClassInfo) params.get("classInfo");
        final String packageName = (String)params.get("packageName");
        final Boolean useProtobuf = (Boolean)params.get("useProtobuf");
        String moduleName = (String)params.get("moduleName");
        final Boolean useMapstruct = false;
        String className = classInfo.getClassName();
        String classNameLowCase = className.toLowerCase();

        //Backend section
        Map<String, String> backendResult = new LinkedHashMap<>(16);
        String sectionKey = "backend";
        String filePathPrefix = classNameLowCase + File.separator;
        moduleName = StringUtils.isNotBlank(moduleName)?moduleName+File.separator:"";

        String filePath = filePathPrefix + "model" + File.separator +  "entity" + File.separator +moduleName+ className + ".java";
        String templatePath = "code-generator/backend/base/Brand.java";
        generateCodeFile(sectionKey, "domain", filePath, templatePath, params, fileNameMap, backendResult);

        filePath = filePathPrefix + "model" + File.separator +  "dto" + File.separator +moduleName+ className + "DTO.java";
        templatePath = "code-generator/backend/base/BrandDTO.java";
        generateCodeFile(sectionKey, "DTO", filePath, templatePath, params, fileNameMap, backendResult);

        filePath = filePathPrefix+ "model" + File.separator + "dto" + File.separator +moduleName+ className + "QueryDTO.java";
        templatePath = "code-generator/backend/base/BrandQueryDTO.java";
        generateCodeFile(sectionKey, "QueryDTO", filePath, templatePath, params, fileNameMap, backendResult);

        filePath = filePathPrefix + "mapper/pgsql" + File.separator +moduleName+ className + "Mapper.java";
        templatePath = "code-generator/backend/base/BrandMapper.java";
        generateCodeFile(sectionKey, "mapper", filePath, templatePath, params, fileNameMap, backendResult);

        filePath = filePathPrefix + "service" + File.separator +moduleName+ className + "Service.java";
        templatePath = "code-generator/backend/base/BrandService.java";
        generateCodeFile(sectionKey, "service", filePath, templatePath, params, fileNameMap, backendResult);

        filePath = filePathPrefix + "service" + File.separator+"impl"+ File.separator+moduleName+ className + "ServiceImpl.java";
        templatePath = "code-generator/backend/base/BrandServiceImpl.java";
        generateCodeFile(sectionKey, "serviceImpl", filePath, templatePath, params, fileNameMap, backendResult);

//        if(useMapstruct){
//            filePath = filePathPrefix + "service" + File.separator + "mapper" + File.separator + className + "Mapper.java";
//            templatePath = "code-generator/backend/base/MemberMapper.java";
//            generateCodeFile(sectionKey, "mapper", filePath, templatePath, params, fileNameMap, backendResult);
//        }else{
//            filePath = filePathPrefix + "service" + File.separator + "mapper" + File.separator + className + "Mapper.java";
//            templatePath = "code-generator/backend/base/BrandMapper.java";
//            generateCodeFile(sectionKey, "mapper", filePath, templatePath, params, fileNameMap, backendResult);
//        }


        filePath = filePathPrefix + "controller" + File.separator +moduleName+ className + "Controller.java";
        templatePath = "code-generator/backend/base/BrandController.java";
        generateCodeFile(sectionKey, "controller", filePath, templatePath, params, fileNameMap, backendResult);


        if(useProtobuf != null && useProtobuf){
            filePath = "proto" + File.separator + packageName.replaceAll("\\.", Matcher.quoteReplacement(File.separator))
                    + File.separator +"proto" + File.separator + classInfo.getTableName().toLowerCase() + ".proto";
            templatePath = "code-generator/backend/proto/brand.proto";
            generateCodeFile(sectionKey, "protobuf", filePath, templatePath, params, fileNameMap, backendResult);
        }


        result.put(sectionKey, backendResult);

        //backend1.0 section
//        Map<String, String> backend_1_Result = new LinkedHashMap<>(16);
//        sectionKey = "backend_1";
//        filePath = filePathPrefix + "domain" + File.separator + className + ".java";
//        templatePath = "code-generator/backend/AuthorityMenu.java";
//        generateCodeFile(sectionKey, "domain", filePath, templatePath, params, fileNameMap, backend_1_Result);
//
//        filePath = filePathPrefix + "dto" + File.separator + className + "DTO.java";
//        templatePath = "code-generator/backend/AuthorityMenuDTO.java";
//        generateCodeFile(sectionKey, "DTO", filePath, templatePath, params, fileNameMap, backend_1_Result);
//
//        filePath = filePathPrefix + "repository" + File.separator + className + "Repository.java";
//        templatePath = "code-generator/backend/AuthorityMenuRepository.java";
//        generateCodeFile(sectionKey, "repository", filePath, templatePath, params, fileNameMap, backend_1_Result);
//
//        filePath = filePathPrefix + "service" + File.separator + className + "Service.java";
//        templatePath = "code-generator/backend/AuthorityMenuService.java";
//        generateCodeFile(sectionKey, "service", filePath, templatePath, params, fileNameMap, backend_1_Result);
//
//        filePath = filePathPrefix + "controller" + File.separator + className + "Controller.java";
//        templatePath = "code-generator/backend/AuthorityMenuController.java";
//        generateCodeFile(sectionKey, "controller", filePath, templatePath, params, fileNameMap, backend_1_Result);
//
//        result.put(sectionKey, backend_1_Result);

        //Web section




        Map<String, String> webResult = new LinkedHashMap<>(16);
        sectionKey = "web";
        filePath = "services" + File.separator + classNameLowCase + ".service.ts";
        templatePath = "code-generator/web/staff/staff.service.ts";
        generateCodeFile(sectionKey, "service", filePath, templatePath, params, fileNameMap, webResult);

        filePath =  "views" + File.separator + classNameLowCase + "-list.ts";
        templatePath = "code-generator/web/staff/staff-list.ts";
        generateCodeFile(sectionKey, "page-ts", filePath, templatePath, params, fileNameMap,
                webResult);

        filePath =  "views" + File.separator + className + "List.vue";
        templatePath = "code-generator/web/staff/StaffList.vue";
        generateCodeFile(sectionKey, "view", filePath, templatePath, params, fileNameMap,
                webResult);


        result.put(sectionKey, webResult);


        return result;
    }

    private void generateCodeFile(String sectionKey, String key, String filePath, String templatePath, Map<String, Object> params, Map<String, String> filePathMap, Map<String, String> backendResult) throws IOException, TemplateException {
        backendResult.put(key, freemarkerTool.processString(templatePath, params));
        String filePathKey = getFilePathKey(sectionKey, key);
        if (filePathMap.get(filePathKey) != null) {
            throw new IllegalArgumentException("出现重复Key:" + filePathKey);
        }
        filePathMap.put(filePathKey, filePath);
    }

    private String getFilePathKey(String sectionKey, String key) {
        return sectionKey + ":" + key;
    }

    private String zipFiles(Map<String, String> fileNameMap, Map<String, Object> params, Map<String, Map<String, String>> result) throws IOException {
        ClassInfo classInfo = (ClassInfo) params.get("classInfo");
        String className = classInfo.getClassName();
        String prefix = "gen-code." + RandomUtils.nextInt();
        String fileName = prefix + File.separator + className + ".zip";

        File dir = new File(System.getProperty("java.io.tmpdir") + File.separator + prefix);
        dir.mkdir();

        String zipFileName = System.getProperty("java.io.tmpdir") + File.separator + fileName;
        log.info("zipFileName " + zipFileName);
        ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFileName));
        try {
            generateZipFiles(fileNameMap, result, zipOutputStream);

        } finally {
            zipOutputStream.flush();
            zipOutputStream.close();


        }


        return fileName;

    }

    /**
     * Compress the given directory with all its files.
     */
    private void zipFiles4Download(Map<String, String> fileNameMap, Map<String, Map<String, String>> result, HttpServletResponse response) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipOutputStream zos = new ZipOutputStream(baos);

        try {
            generateZipFiles(fileNameMap, result, zos);

        } finally {
            zos.flush();
            zos.close();
        }


        try {
            response.getOutputStream().write(baos.toByteArray());
            response.flushBuffer();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            baos.close();
        }

    }

    private void generateZipFiles(Map<String, String> filePathMap, Map<String, Map<String, String>> result, ZipOutputStream zos) throws IOException {

        for (String sectionKey : result.keySet()) {
            Map<String, String> resultMap = result.get(sectionKey);
            for (String key : resultMap.keySet()) {
                String fileContent = resultMap.get(key);
                String folderName = sectionKey;
                String filePathKey = getFilePathKey(sectionKey,key);
                String fileName = filePathMap.get(filePathKey);
                String filePath = folderName + File.separator + fileName;
                ZipEntry ze = new ZipEntry(filePath);
                zos.putNextEntry(ze);
                byte[] data = fileContent.getBytes("UTF-8");
                zos.write(data, 0, data.length);
                zos.closeEntry();

                log.info(filePath + " " + ze.getCompressedSize()
                        + "/" + ze.getSize());

            }
        }

    }

    private Map<String, Object> generateParams(String tableSql, @RequestParam(required = false, defaultValue = "Tony Luo") String authorName,
                                               @RequestParam(required = false, defaultValue = "com.boss") String packageNamePrefix,
                                               @RequestParam(required = false, defaultValue = "product") String moduleName,String tablePrefix) throws IOException {


        // code genarete
        Map<String, Object> params = new HashMap<String, Object>(16);
        // parse table
        ClassInfo classInfo = CodeGeneratorTool.processTableIntoClassInfo(tableSql,tablePrefix);
        moduleName = StringUtils.isBlank(moduleName) ? classInfo.getClassNameFirstLow() : moduleName;
        params.put("classInfo", classInfo);
        params.put("authorName", authorName);
//        String newPackageName = packageNamePrefix + "." + moduleName.toLowerCase() + "." + classInfo.getClassName().toLowerCase();
        String newPackageName = packageNamePrefix;
//        if(!StringUtils.isBlank(moduleName)){
//            newPackageName = newPackageName + "." + moduleName.toLowerCase();
//        }
        params.put("packageName", newPackageName);
        params.put("moduleName", moduleName);
        return params;
    }

}